using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
using System.Web.Hosting;
using System.Web;
using EPiServer.Security;
using System.IO;
//using VppSample;
using System.ServiceModel.Syndication;
using NCMIS.ServiceModel.Syndication;
using NCMIS.ObjectModel;
using NCMIS.ObjectModel.MetaData;
using NCMIS.ObjectModel.Rest;
using NCMIS.Client;
using System.Diagnostics;
using System.Xml;

namespace EPiServer.Web.Hosting
{
    public class CmisDirectory : UnifiedDirectory
    {
        // List of this subdirectories stored in this directory in the virtual file system
        private IList<CmisDirectory> _directories = null; // new List<CmisDirectory>();
        // List of this files stored in this directory in the virtual file system
        private IList<CmisFile> _files = null; // new List<CmisFile>();

        private CmisWebRequest _cmisWebReq;
        private CmisDirectory _parent;

        /// <summary>
        /// CmisDirectory constructor.
        /// </summary>
        /// <param name="provider"></param>
        /// <param name="virtualPath"></param>
        public CmisDirectory(VirtualPathUnifiedProvider provider, string virtualPath)
            : base(provider, virtualPath, null, true)
        {
            // Create a (CMIS) web request
            _cmisWebReq = provider.GetCmisWebRequest();
        }

        #region Properties

        /// <summary>
        /// URI pointing to this directory
        /// </summary>
        public Uri SelfURI
        {
            get;
            set;
        }

        /// <summary>
        /// URI pointing to this directory's children
        /// </summary>
        public Uri ChildrenURI
        {
            get;
            set;
        }

        /// <summary>
        /// URI pointing to this directory's parents
        /// </summary>
        public Uri UpURI
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the children of this directory
        /// </summary>
        public override IEnumerable Children
        {
            get
            {
                List<object> children = new List<object>();

                foreach (object dir in Directories)
                {
                    children.Add(dir);
                }

                foreach (object file in Files)
                {
                    children.Add(file);
                }

                return children;
            }
        }

        protected IList<CmisDirectory> InternalDirectories
        {
            get
            {
                if (null == _directories)
                {
                    this.FetchRepositoryContent(this.ChildrenURI);
                }

                return _directories;
            }
            set
            {
                _directories = value;
            }
        }

        protected IList<CmisFile> InternalFiles
        {
            get
            {
                if (null == _files)
                {
                    this.FetchRepositoryContent(this.ChildrenURI);
                }

                return _files;
            }
            set
            {
                _files = value;
            }
        }

        /// <summary>
        /// Gets the subdirectories contained in this directory. 
        /// </summary>
        public override IEnumerable Directories
        {
            get
            {
                return InternalDirectories;
            }
        }

        /// <summary>
        /// Gets the files contained in this directory.
        /// </summary>
        public override IEnumerable Files
        {
            get
            {
                return InternalFiles;
            }
        }

        /// <summary>
        /// Gets the parent directory of this directory.
        /// </summary>
        public override UnifiedDirectory Parent
        {
            get
            {
                return _parent;
            }
        }

        /// <summary>
        /// Checks if this directory is the root (i.e. if the parent is null).
        /// </summary>
        public override bool IsFirstLevel
        {
            get
            {
                return (Parent == null);
            }
        }

        /// <summary>
        /// This implementation doesn't support versioning.
        /// </summary>
        public override bool IsVersioningSupported
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Give full access to everyone.
        /// </summary>
        public override AccessControlList ACL
        {
            get
            {
                AccessControlList acl = new AccessControlList();
                if (BypassAccessCheck)
                {
                    acl.Add(new AccessControlEntry("Everyone", AccessLevel.FullAccess, SecurityEntityType.Role));
                }
                return acl;
            }
        }

        #endregion

        #region Helper methods

        /// <summary>
        /// Checks if this directory contains a file with name.
        /// </summary>
        /// <param name="name">Name of file to check.</param>
        /// <returns>File exists flag.</returns>
        internal bool ContainsFile(string name)
        {
            return (GetFile(name) != null);
        }

        /// <summary>
        /// Checks if this directory contains a subdir with name.
        /// </summary>
        /// <param name="name">Name of directory to check.</param>
        /// <returns>Directory exists flag.</returns>
        internal bool ContainsDirectory(string name)
        {
            return (GetDirectory(name) != null);
        }

        #endregion

        #region Create methods

        /// <summary>
        /// Creates a new subdirectory in the specified directory in the repository.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public override UnifiedDirectory CreateSubdirectory(string path)
        {
            if (ContainsDirectory(path))
            {
                return null;
            }

            if (ChildrenURI == null)
            {
                return null;
            }

            // Create a new subdirectory at the specified path
            CmisDirectory newDir = new CmisDirectory(Provider, Provider.CombineVirtualPaths(VirtualPath, path));
            // Wrap the CMIS (folder) object and create it in the repository
            SyndicationItem folderEntry = CmisFolder.CreateFolder(_cmisWebReq, newDir.Name, ChildrenURI);

            // Get links 
            SyndicationLink selfLink = CmisSyndicationItem.GetLink(folderEntry, CmisLinkRel.Self);
            SyndicationLink childrenLink = CmisSyndicationItem.GetLink(folderEntry, CmisLinkRel.Down); // TODO: There may be more than one down links, always returns first
            SyndicationLink upLink = CmisSyndicationItem.GetLink(folderEntry, CmisLinkRel.Up);

            // Set links
            if (selfLink != null)
            {
                newDir.SelfURI = selfLink.Uri;
            }
            if (childrenLink != null)
            {
                newDir.ChildrenURI = childrenLink.Uri;
            }
            if (upLink != null)
            {
                newDir.UpURI = upLink.Uri;
            }

            // Update virtual file system
            InternalDirectories.Add(newDir);

            return newDir;
        }

        /// <summary>
        /// Fetches the current subdirectory's content (files and folders) from the repository.
        /// </summary>
        /// <param name="childrenFeedURI">The children feed URI from which to obtain
        /// the children feed.</param>
        public void FetchRepositoryContent(Uri childrenFeedURI)
        {
            // If cached content doesn't exist, fetch new content from the repository
            SyndicationFeed childrenFeed = _cmisWebReq.GetSyndicationFeed(childrenFeedURI.AbsoluteUri);

            _directories = new List<CmisDirectory>();
            _files = new List<CmisFile>();

            foreach (SyndicationItem entry in childrenFeed.Items)
            {
                // Extract the CMIS object from the wrapper
                CmisObject cmisObject = CmisSyndicationItem.GetObject(entry);

                // Intended way of getting access rights: the Acl.Permissions property should contain 
                // a list of ACEs associated with each object (the GetObject(entry) method updates this property). 
                /*NCMIS.AccessControl.AccessControlEntry[] aceList = cmisObject.Acl.Permissions;    // Acl.Permissions returns null??

                // Print contents of each ACE
                foreach(NCMIS.AccessControl.AccessControlEntry ace in aceList)
                {
                    System.Diagnostics.Debug.WriteLine("Principal" + ace.Principal.PrincipalId);

                    foreach (string p in ace.Permissions)
                    {
                        System.Diagnostics.Debug.WriteLine("Permission:" + p);
                    }
                }*/

                string baseTypeId = cmisObject.Properties.BaseTypeId.SingleValue;

                // If the type is cmis:folder, create a new subdirectory at the specified path
                if (baseTypeId == "cmis:folder")
                {
                    string directoryName = cmisObject.Properties.Name.SingleValue;
                    string relativePath = string.Format("{0}/", directoryName);

                    CmisDirectory newDir = new CmisDirectory(Provider, Provider.CombineVirtualPaths(VirtualPath, directoryName));

                    // Set directory (URI) links
                    SyndicationLink selfLink = CmisSyndicationItem.GetLink(entry, CmisLinkRel.Self);
                    SyndicationLink childrenLink = CmisSyndicationItem.GetLink(entry, CmisLinkRel.Down); // TODO: There may be more than one down links, always returns first
                    SyndicationLink upLink = CmisSyndicationItem.GetLink(entry, CmisLinkRel.Up);

                    if (selfLink != null)
                    {
                        newDir.SelfURI = selfLink.Uri;
                    }
                    if (childrenLink != null)
                    {
                        newDir.ChildrenURI = childrenLink.Uri;
                    }
                    if (upLink != null)
                    {
                        newDir.UpURI = upLink.Uri;
                    }

                    // Update virtual file system
                    _directories.Add(newDir);
                }

                // If the type is cmis:document, create a file at the specified path
                if (baseTypeId == "cmis:document")
                {
                    string fileName = cmisObject.Properties.ContentStreamFileName.SingleValue;

                    int contentLength = cmisObject.Properties.ContentStreamLength.SingleValue ?? 0;

                    CmisFile newFile = new CmisFile(this, Provider, Provider.CombineVirtualPaths(VirtualPath, fileName), true, contentLength);

                    // Set the self-URI of the file
                    newFile.SelfURI = CmisSyndicationItem.GetLink(entry, CmisLinkRel.Self).Uri;

                    // Store ContentURI so content can be loaded when needed
                    newFile.ContentURI = (entry.Content as UrlSyndicationContent).Url;

                    // Update virtual file system
                    _files.Add(newFile);
                }
            }
        }

        /// <summary>
        /// Creates a new file in the repository.
        /// </summary>
        /// <param name="name">The name of the file</param>
        /// <returns></returns>
        public override UnifiedFile CreateFile(string name)
        {
            if (ContainsFile(name))
            {
                return null;
            }

            CmisFile newFile = new CmisFile(this, Provider, Provider.CombineVirtualPaths(VirtualPath, name), BypassAccessCheck, 0L);

            // NOTE: File content is written in the CmisFileStream class.
            newFile.ShouldCreateFile = true;

            // Pass the parents childrenURI to CmisFileStream where the web request is made
            newFile.ParentsChildrenURI = this.ChildrenURI;


            // Update virtual file system
            InternalFiles.Add(newFile);

            return newFile;
        }

        /// <summary>
        /// Creates a new file with name in this directory. Id can be used to identify the
        /// file in a backend system such as a database. This is of no use in this implementation.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public override UnifiedFile CreateFile(string name, Guid id)
        {
            return CreateFile(name);
        }

        #endregion

        #region Delete methods

        /// <summary>
        /// Deletes this directory and everything below.
        /// </summary>
        public override void Delete()
        {
            if (ChildrenURI != null)
            {
                // Delete this directory's children from the server
                _cmisWebReq.DeleteEntry(ChildrenURI.AbsoluteUri);
                // Delete this directory's childrens' references in the VPP
                InternalDirectories.Clear();
                InternalFiles.Clear();
                InternalDirectories = null;
                InternalFiles = null;

                // Delete this directory from the server
                //_cmisWebReq.DeleteEntry(SelfURI.AbsoluteUri);     // Not needed??
                // Delete this directory's reference in the VPP
                CmisDirectory parent = Parent as CmisDirectory;
                if (parent != null)
                {
                    parent.DeleteDirectoryRefByName(Name);
                }
            }
        }

        /// <summary>
        /// Deletes a file in the repository.
        /// </summary>
        /// <param name="fileURI"></param>
        public void DeleteFile(Uri fileURI)
        {
            _cmisWebReq.DeleteEntry(fileURI.AbsoluteUri);
        }

        /// <summary>
        /// Deletes the reference to a file by it's name from the virtual file system.
        /// </summary>
        /// <param name="name"></param>
        public void DeleteFileRefByName(string name)
        {
            for (int i = 0; i < InternalFiles.Count; i++)
            {
                if (String.Compare(InternalFiles[i].Name, name, true) == 0)
                {
                    InternalFiles.RemoveAt(i);
                    return;
                }
            }
        }

        /// <summary>
        /// Deletes the reference to a directory by it's name from the virtual file system.
        /// </summary>
        /// <param name="name"></param>
        public void DeleteDirectoryRefByName(string name)
        {
            for (int i = 0; i < InternalDirectories.Count; i++)
            {
                if (String.Compare(InternalDirectories[i].Name, name, true) == 0)
                {
                    InternalDirectories.RemoveAt(i);
                    return;
                }
            }
        }

        #endregion

        #region Get methods

        /// <summary>
        /// Gets the subdirectories in a more object type safe format than VirtualDirectory
        /// class method.
        /// </summary>
        /// <returns></returns>
        public override UnifiedDirectory[] GetDirectories()
        {

            IEnumerable subDirectories = Directories;
            List<CmisDirectory> dirs = new List<CmisDirectory>();
            foreach (CmisDirectory dir in subDirectories)
            {
                dirs.Add(dir);
                dir._parent = this;
            }
            return dirs.ToArray();
        }

        /// <summary>
        /// Gets a subdirectory by its name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        internal CmisDirectory GetDirectory(string name)
        {
            foreach (CmisDirectory dir in Directories)
            {
                if (String.Compare(dir.Name, name, true) == 0)
                {
                    return dir;
                }
            }
            return null;
        }

        /// <summary>
        /// Gets the files in a more object type safe format than VirtualDirectory
        /// class method.
        /// </summary>
        /// <returns></returns>
        public override UnifiedFile[] GetFiles()
        {
            IEnumerable sampleFiles = Files;
            List<CmisFile> files = new List<CmisFile>();
            foreach (CmisFile file in sampleFiles)
            {
                files.Add(file);
            }
            return files.ToArray();
        }

        /// <summary>
        /// Gets a file by its name.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        internal CmisFile GetFile(string name)
        {
            foreach (CmisFile file in Files)
            {
                if (String.Compare(file.Name, name, true) == 0)
                {
                    return file;
                }
            }
            return null;
        }

        #endregion

        #region Move, copy, search

        /// <summary>
        /// Moves the current directory and its child content to the specified virtual directory.
        /// </summary>
        /// <param name="virtualDir"></param>
        public override void MoveTo(string virtualDir)
        {
            // Let hosting env. get the directory reference to avoid file system mixing.
            CmisDirectory destinationDir = HostingEnvironment.VirtualPathProvider.GetDirectory(virtualDir) as CmisDirectory;
            if (destinationDir == null)
            {
                //destination dir does not exist, check if it is a rename
                if (String.Equals(Parent.VirtualPath, VirtualPathUtility.GetDirectory(VirtualPathUtility.RemoveTrailingSlash(virtualDir)), StringComparison.OrdinalIgnoreCase))
                {
                    destinationDir = (CmisDirectory)Parent.CreateSubdirectory(VirtualPathUtility.GetFileName(VirtualPathUtility.RemoveTrailingSlash(virtualDir)));
                }
            }
            if (destinationDir != null)
            {
                CopyTo(virtualDir);
                Delete();
            }
        }

        /// <summary>
        /// Copies the directory and all subcontent to the specified virtual directory. 
        /// </summary>
        /// <param name="virtualDir">Path where to copy directory to.</param>
        public override void CopyTo(string virtualDir)
        {
            // Let hosting env. get the directory reference to avoid file system mixing.
            CmisDirectory destinationDir = HostingEnvironment.VirtualPathProvider.GetDirectory(virtualDir) as CmisDirectory;
            if (destinationDir != null)
            {
                foreach (CmisDirectory subDir in Directories)
                {
                    destinationDir.CreateSubdirectory(subDir.Name);
                }
                foreach (CmisFile file in Files)
                {
                    destinationDir.CreateFile(file.Name);
                }
            }
        }

        /// <summary>
        /// Searches for a file or directory.
        /// 
        /// TO DO: Fix filepath. Currently always /cmisdoc/
        /// </summary>
        /// <param name="query"></param>
        /// <returns></returns>
        public override UnifiedSearchHitCollection Search(UnifiedSearchQuery query)
        {
            UnifiedSearchHitCollection hits = new UnifiedSearchHitCollection();
            if (query != null)
            {
                // Dictionary to hold search parameters
                Dictionary<string, Object> paramDict = new Dictionary<string, Object>();
                paramDict.Add("FileName", query.FileNamePattern);
                paramDict.Add("FreeText", query.FreeTextQuery);
                paramDict.Add("ModAfter", query.ModifiedFrom);
                paramDict.Add("ModBefore", query.ModifiedTo);
                paramDict.Add("CurrentDir", this.SelfURI);

                System.Diagnostics.Debug.WriteLine(SelfURI);

                // Perform search
                SyndicationFeed results = ClientCmisQuery.performQuery(_cmisWebReq, paramDict);

                // Process search hits
                foreach (SyndicationItem entry in results.Items)
                {
                    CmisObject tempObject = CmisSyndicationItem.GetObject(entry);

                    string fileName = tempObject.Properties.ContentStreamFileName.SingleValue;
                    string filePath = Provider.CombineVirtualPaths(VirtualPath, fileName);      // TO DO: Fix filepath. Currently always /cmisdoc/

                    UnifiedSearchHit hit = new UnifiedSearchHit(filePath, 1, fileName, "[No characterization preview...]");
                    hits.Add(hit);
                }

                // ** Consider removing ** 
                //// Search through the files in this directory.
                //foreach (CmisFile file in GetFiles())
                //{
                //    CmisSummary summary = (CmisSummary)file.Summary;

                //    // Try to match file name
                //    if (!String.IsNullOrEmpty(query.FileNamePattern))
                //    {

                //        //string pattern = query.FileNamePattern; //@"[/.*&&[^~]]$"   // Use regular expressions to match file names?
                //        //System.Text.RegularExpressions.Match match = System.Text.RegularExpressions.Regex.Match(file.Name, pattern, 
                //        //    System.Text.RegularExpressions.RegexOptions.IgnoreCase);

                //        //if(match.Success)
                //        bool found = true;
                //        if (found)
                //        {
                //            // Do something with the file if found
                //            System.Diagnostics.Debug.WriteLine("Found");
                //        }
                //        continue;
                //        // TODO: Filter file if search pattern doesn't match. For example the 
                //        // file is named "donald_duck.gif" and search pattern is "*.jpg".

                //        // Sample implementation filters everything if any user input.
                //        //continue;
                //    }

                //    // Our file is modified BEFORE.
                //    if (query.ModifiedFrom != DateTime.MinValue)
                //    {
                //        if (file.Changed < query.ModifiedFrom)
                //        {
                //            continue;
                //        }
                //    }

                //    // Our file is modified AFTER.
                //    if (query.ModifiedTo != DateTime.MinValue)
                //    {
                //        if (file.Changed > query.ModifiedTo)
                //        {
                //            continue;
                //        }
                //    }

                //    // Free text -> Summary match.
                //    if (!String.IsNullOrEmpty(query.FreeTextQuery.Trim()))
                //    {
                //        bool foundMatch = StringSearch(summary.Author, query.FreeTextQuery);
                //        foundMatch |= StringSearch(summary.Title, query.FreeTextQuery);
                //        foundMatch |= StringSearch(summary.Subject, query.FreeTextQuery);
                //        foundMatch |= StringSearch(summary.Comments, query.FreeTextQuery);
                //        foundMatch |= StringSearch(summary.Category, query.FreeTextQuery);
                //        foundMatch |= StringSearch(summary.Keywords, query.FreeTextQuery);
                //        if (!foundMatch)
                //        {
                //            continue;
                //        }
                //    }

                //    // TODO: Summary -> Summary match.

                //    UnifiedSearchHit hit = new UnifiedSearchHit(file.VirtualPath, 1, file.Name, "[No characterization preview...]");
                //    hits.Add(hit);
                //}
            }
            return hits;
        }

        /* Consider removing */
        /// <summary>
        /// Searches in src string for occurance of look4 string by IndexOf.
        /// </summary>
        /// <param name="src"></param>
        /// <param name="look4"></param>
        /// <returns></returns>
        //private bool StringSearch(string src, string look4)
        //{
        //    // Validate src.
        //    if (String.IsNullOrEmpty(src) || src.Trim().Length == 0)
        //    {
        //        return false;
        //    }
        //    // Validate look4.
        //    if (String.IsNullOrEmpty(look4) || look4.Trim().Length == 0)
        //    {
        //        return false;
        //    }
        //    // "Search"...
        //    return (src.IndexOf(look4, StringComparison.InvariantCultureIgnoreCase) > -1);
        //}

        #endregion
    }
}
